Exercises

Link to the source.

Solutions

Roman emperors

The first exercise uses a dataset about roman emperors from the tidytuesday project (link).

library(tidyverse)
library(lubridate)

fix_emperors <- function(data) {
  data %>% 
    mutate(
      birth = case_when(
        index %in% c(1, 2, 4, 6) ~ update(birth, year = -year(birth)),
        TRUE                     ~ birth
      ),
      reign_start = case_when(
        index == 1 ~ update(reign_start, year = -year(reign_start)),
        TRUE       ~ reign_start
      )
    )
}


emperors <- read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-08-13/emperors.csv") %>% 
  fix_emperors()

This is what our data looks like:

emperors

Let’s answer some questions! What was the most popular way to rise to power?

emperors %>% 
  count(rise, sort = TRUE)

Turns out, the best way to become an emperor is to be born an emperor! But there is still a decent chance to seize power by force.

Emperors live a dangerous life!

I what are the most common causes of death among roman emperors? What (or who) killed them?

emperors %>% 
  group_by(cause, killer) %>% 
  summarise(n = n()) %>% 
  arrange(desc(n))

While diseases as a natural cause still make up for a significant portion of deaths, they are quickly followed by less natural causes such as assassination. Mostly by other emperors, but there are also two wives among the killers.

There are of course multiple ways of graphing this. Here, we count the causes and killers and display them as a dodged barchart.

emperors %>% 
  count(cause, killer, sort = TRUE) %>% 
  ggplot(aes(cause, n, group = paste(cause, killer), fill = killer)) + 
  geom_col(position = position_dodge2(preserve = "single", width = 1.1))

geom_bar can do the counting for us and we switched to a stacked bar chart and a different color palette:

emperors %>% 
  ggplot(aes(cause, group = paste(cause, killer), fill = killer)) + 
  geom_bar(color = "black") +
  scale_fill_viridis_d()

This allows us to compare the main causes as well as make comparisons within each cause.

Which dynasty was the most successful? Firstly, how often did each dynasty reign?

emperors %>% 
  count(dynasty, sort = TRUE)

A simple count may be enough here, but we like our pretty graphs. So we play around with a barplot again.

emperors %>% 
  count(dynasty) %>% 
  mutate(dynasty = fct_reorder(dynasty, n)) %>% 
  ggplot(aes(dynasty, n)) +
  geom_col() +
  geom_text(aes(label = n), color = "white", vjust = 1.3, fontface = "bold")

That’s a lot of Gordians!

But how long where the reigns?

We found out that there are two different ways of calculating the reign length and that those are not equivalent.

If we calculate how long each emperor reigned and then sum those up within each dynasty:

emperors %>% 
  mutate(reign = reign_end - reign_start) %>% 
  group_by(dynasty) %>% 
  summarise(reign = sum(reign)) %>% 
  arrange(desc(reign))

We actually get higher numbers than by subtracting the reign start of the first emperor in each dynasty from the reign end of the last emperor in the dynasty!

emperors %>% 
  arrange(reign_start) %>% 
  group_by(dynasty) %>% 
  summarise(reign = last(reign_end) - first(reign_start)) %>% 
  arrange(desc(reign))

The reason for this is that some emperors started their reign with the previous emperor also still reigning:

emperors %>% 
  arrange(reign_start) %>% 
  mutate(time_between = time_length(reign_start - lag(reign_end), unit = "year") ) %>% 
  ggplot(aes(time_between)) +
  geom_histogram() +
  labs(x = "Years after the previous emperor") +
  theme_minimal()

In some cases this is probably a data quality issue, in some cases it might actually be the case that an emperor decided to now be reigning while another was still on the throne.

emperors %>% 
  mutate(name = fct_reorder(name, reign_start)) %>% 
  ggplot(aes(x = reign_start, xend = reign_end, y = name, yend = name, color = dynasty)) +
  geom_segment(size = 2) +
  # geom_text(aes(label = name)) +
  scale_color_viridis_d() +
  theme_minimal() +
  theme(legend.position = "bottom") +
  labs(x = "Time", y = "", title = "Roman Emperors") +
  scale_y_discrete()

There was a question about coloring in the names on the y-axis, here is a package to help you with that: https://wilkelab.org/ggtext/

Which dynasty would you rather be a part of, if your goal is to live the longest?

plt <- emperors %>% 
  mutate(life = death - birth) %>% 
  filter(!is.na(life)) %>% 
  ggplot(aes(dynasty, life)) +
  stat_summary(color = "grey50") +
  geom_point(aes(text = name)) +
  coord_flip() +
  theme_minimal()

plotly::ggplotly(plt)

As always, the answer depends. Are we the gambling type? Gordians have pretty high ceiling for their lifespan but also a pretty low floor…

Dairy Products in the US

Another dataset (link) concerns dairy product consumption per person in the US across a number of years.

dairy <- read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-01-29/milk_products_facts.csv")

Note: after playing a around with the data we would normally then put all the data cleaning steps up here where we import the data so that we see it straight away. Sometimes I like to call the initially imported dataset <name>_raw, e.g. dairy_raw and then after cleaning the data I store it in a variable like dairy_tidy or dairy.

This is whay the initial data looks like:

dairy

All masses are given in lbs (pounds), can you convert them to kg?

There are multiple ways to do that!

lbs_to_kg <- function(x) x / 2.2

dairy %>% 
  mutate(across(-year, lbs_to_kg))

or with an anonymous function:

dairy %>% 
  mutate(across(-year, ~ .x / 2.2))

But we can also remember our tidy data principles and do a pivot first!

dairy <- dairy %>% 
  pivot_longer(-year, names_to = "product", values_to = "consumption") %>% 
  mutate(consumption = consumption / 2.2)

All approaches are valid, but we like the tidy data format for the rest of this analysis. Note though, that the first approach had the added benefit of giving a reasonable name to our calculation lbs_to_kg, so this will be nice if we have to come back to it later to see what we did. Readability counts! We could have combined the first and third approach as well.

Which products lost their customer base over time, which ones won? Which products have the greatest absolute change in production when estimated with a straight line?

Visually:

dairy %>% 
  ggplot(aes(year, consumption, color = product)) +
  geom_smooth(method = "lm", alpha = 0.3) +
  geom_line() +
  facet_wrap(~product, scales = "free") +
  guides(color = "none") +
  theme_minimal() +
  labs(y = "Consumption [kg]") +
  scale_x_continuous(n.breaks = 5)

Numerically:

models <- dairy %>%
  group_by(product) %>% 
  summarise(
    model = list(lm(consumption ~ year)),
    slope = map_dbl(model, ~ broom::tidy(.x)$estimate[2])
  )

models %>% 
  arrange(abs(slope))

Further notes

Tidy evaluatio

By now I have hyped up writing your own functions quite a bit and there will inevitably come the point when you will try to write a function that works like one of the tidyverse functions.

Unfortunately the trade off of being nice to work with interactively, like just referring to columns in a dataframe by their name inside of tidyverse functions, comes at a price. If we want the same, we need to take on extra step.

In the function test below, if we didn’t have the double curly braces {{}}, dplyr would search for a column named column in the dataset! This is of course not there, so you get an error. The curly-curly operator allows us to tell dplyr that this name is just a placeholder for what the user of our function will supply:

test <- function(data, column) {
  data %>% 
    count( {{ column }} )
}

test(emperors, rise)

Finding help

Learning how to find help and ask for help is crucial. The most important tip is learning how a “Reproducible Example”, a “reprex” works

https://www.tidyverse.org/help/#reprex

More Resources.

LS0tCnRpdGxlOiAiU29sdXRpb24gNCIKYXV0aG9yOiAiSmFubmlrIEJ1aHIiCmRhdGU6ICIxMi8xMS8yMDIxIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6IAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmBgYAoKIyBFeGVyY2lzZXMKCltMaW5rXShodHRwczovL2ptYnVoci5kZS9kYXRhaW50cm8vZnVuY3Rpb25hbC1wcm9ncmFtbWluZy5odG1sKSB0byB0aGUgc291cmNlLgoKIyBTb2x1dGlvbnMKCiMjIFJvbWFuIGVtcGVyb3JzCgpUaGUgZmlyc3QgZXhlcmNpc2UgdXNlcyBhIGRhdGFzZXQgYWJvdXQgcm9tYW4gZW1wZXJvcnMKZnJvbSB0aGUgdGlkeXR1ZXNkYXkgcHJvamVjdAooW2xpbmtdKGh0dHBzOi8vZ2l0aHViLmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvdHJlZS9tYXN0ZXIvZGF0YS8yMDE5LzIwMTktMDgtMTMpKS4KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShsdWJyaWRhdGUpCgpmaXhfZW1wZXJvcnMgPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGEgJT4lIAogICAgbXV0YXRlKAogICAgICBiaXJ0aCA9IGNhc2Vfd2hlbigKICAgICAgICBpbmRleCAlaW4lIGMoMSwgMiwgNCwgNikgfiB1cGRhdGUoYmlydGgsIHllYXIgPSAteWVhcihiaXJ0aCkpLAogICAgICAgIFRSVUUgICAgICAgICAgICAgICAgICAgICB+IGJpcnRoCiAgICAgICksCiAgICAgIHJlaWduX3N0YXJ0ID0gY2FzZV93aGVuKAogICAgICAgIGluZGV4ID09IDEgfiB1cGRhdGUocmVpZ25fc3RhcnQsIHllYXIgPSAteWVhcihyZWlnbl9zdGFydCkpLAogICAgICAgIFRSVUUgICAgICAgfiByZWlnbl9zdGFydAogICAgICApCiAgICApCn0KCgplbXBlcm9ycyA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDE5LzIwMTktMDgtMTMvZW1wZXJvcnMuY3N2IikgJT4lIAogIGZpeF9lbXBlcm9ycygpCmBgYAoKVGhpcyBpcyB3aGF0IG91ciBkYXRhIGxvb2tzIGxpa2U6CgpgYGB7cn0KZW1wZXJvcnMKYGBgCgpMZXQncyBhbnN3ZXIgc29tZSBxdWVzdGlvbnMhCldoYXQgd2FzIHRoZSBtb3N0IHBvcHVsYXIgd2F5IHRvIHJpc2UgdG8gcG93ZXI/CgpgYGB7cn0KZW1wZXJvcnMgJT4lIAogIGNvdW50KHJpc2UsIHNvcnQgPSBUUlVFKQpgYGAKClR1cm5zIG91dCwgdGhlIGJlc3Qgd2F5IHRvIGJlY29tZSBhbiBlbXBlcm9yIGlzIHRvIGJlIGJvcm4gYW4gZW1wZXJvciEKQnV0IHRoZXJlIGlzIHN0aWxsIGEgZGVjZW50IGNoYW5jZSB0byBzZWl6ZSBwb3dlciBieSBmb3JjZS4KCj4gRW1wZXJvcnMgbGl2ZSBhIGRhbmdlcm91cyBsaWZlIQoKSSB3aGF0IGFyZSB0aGUgbW9zdCBjb21tb24gY2F1c2VzIG9mIGRlYXRoIGFtb25nIHJvbWFuCmVtcGVyb3JzPyBXaGF0IChvciB3aG8pIGtpbGxlZCB0aGVtPwoKYGBge3J9CmVtcGVyb3JzICU+JSAKICBncm91cF9ieShjYXVzZSwga2lsbGVyKSAlPiUgCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JSAKICBhcnJhbmdlKGRlc2MobikpCmBgYAoKV2hpbGUgZGlzZWFzZXMgYXMgYSBuYXR1cmFsIGNhdXNlIHN0aWxsIG1ha2UgdXAgZm9yIGEgc2lnbmlmaWNhbnQKcG9ydGlvbiBvZiBkZWF0aHMsIHRoZXkgYXJlIHF1aWNrbHkgZm9sbG93ZWQgYnkgbGVzcyBuYXR1cmFsIGNhdXNlcwpzdWNoIGFzIGFzc2Fzc2luYXRpb24uIE1vc3RseSBieSBvdGhlciBlbXBlcm9ycywgYnV0IHRoZXJlIGFyZSBhbHNvCnR3byB3aXZlcyBhbW9uZyB0aGUga2lsbGVycy4KClRoZXJlIGFyZSBvZiBjb3Vyc2UgbXVsdGlwbGUgd2F5cyBvZiBncmFwaGluZyB0aGlzLgpIZXJlLCB3ZSBjb3VudCB0aGUgY2F1c2VzIGFuZCBraWxsZXJzIGFuZCBkaXNwbGF5IHRoZW0gYXMgYSBkb2RnZWQKYmFyY2hhcnQuCgpgYGB7cn0KZW1wZXJvcnMgJT4lIAogIGNvdW50KGNhdXNlLCBraWxsZXIsIHNvcnQgPSBUUlVFKSAlPiUgCiAgZ2dwbG90KGFlcyhjYXVzZSwgbiwgZ3JvdXAgPSBwYXN0ZShjYXVzZSwga2lsbGVyKSwgZmlsbCA9IGtpbGxlcikpICsgCiAgZ2VvbV9jb2wocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZTIocHJlc2VydmUgPSAic2luZ2xlIiwgd2lkdGggPSAxLjEpKQpgYGAKCmBnZW9tX2JhcmAgY2FuIGRvIHRoZSBjb3VudGluZyBmb3IgdXMgYW5kIHdlIHN3aXRjaGVkIHRvIGEgc3RhY2tlZCBiYXIgY2hhcnQKYW5kIGEgZGlmZmVyZW50IGNvbG9yIHBhbGV0dGU6CgpgYGB7cn0KZW1wZXJvcnMgJT4lIAogIGdncGxvdChhZXMoY2F1c2UsIGdyb3VwID0gcGFzdGUoY2F1c2UsIGtpbGxlciksIGZpbGwgPSBraWxsZXIpKSArIAogIGdlb21fYmFyKGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkKYGBgCgpUaGlzIGFsbG93cyB1cyB0byBjb21wYXJlIHRoZSBtYWluIGNhdXNlcyBhcyB3ZWxsIGFzIG1ha2UgY29tcGFyaXNvbnMKd2l0aGluIGVhY2ggY2F1c2UuCgogCldoaWNoIGR5bmFzdHkgd2FzIHRoZSBtb3N0IHN1Y2Nlc3NmdWw/CkZpcnN0bHksIGhvdyBvZnRlbiBkaWQgZWFjaCBkeW5hc3R5IHJlaWduPwoKYGBge3J9CmVtcGVyb3JzICU+JSAKICBjb3VudChkeW5hc3R5LCBzb3J0ID0gVFJVRSkKYGBgCgpBIHNpbXBsZSBjb3VudCBtYXkgYmUgZW5vdWdoIGhlcmUsIGJ1dCB3ZSBsaWtlIG91ciBwcmV0dHkgZ3JhcGhzLgpTbyB3ZSBwbGF5IGFyb3VuZCB3aXRoIGEgYmFycGxvdCBhZ2Fpbi4KCmBgYHtyfQplbXBlcm9ycyAlPiUgCiAgY291bnQoZHluYXN0eSkgJT4lIAogIG11dGF0ZShkeW5hc3R5ID0gZmN0X3Jlb3JkZXIoZHluYXN0eSwgbikpICU+JSAKICBnZ3Bsb3QoYWVzKGR5bmFzdHksIG4pKSArCiAgZ2VvbV9jb2woKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pLCBjb2xvciA9ICJ3aGl0ZSIsIHZqdXN0ID0gMS4zLCBmb250ZmFjZSA9ICJib2xkIikKYGBgCgpUaGF0J3MgYSBsb3Qgb2YgR29yZGlhbnMhCiAKQnV0IGhvdyBsb25nIHdoZXJlIHRoZSByZWlnbnM/CgpXZSBmb3VuZCBvdXQgdGhhdCB0aGVyZSBhcmUgdHdvIGRpZmZlcmVudCB3YXlzIG9mIGNhbGN1bGF0aW5nCnRoZSByZWlnbiBsZW5ndGggYW5kIHRoYXQgdGhvc2UgYXJlICoqbm90KiogZXF1aXZhbGVudC4KCklmIHdlIGNhbGN1bGF0ZSBob3cgbG9uZyBlYWNoIGVtcGVyb3IgcmVpZ25lZCBhbmQgdGhlbiBzdW0gdGhvc2UKdXAgd2l0aGluIGVhY2ggZHluYXN0eToKCmBgYHtyfQplbXBlcm9ycyAlPiUgCiAgbXV0YXRlKHJlaWduID0gcmVpZ25fZW5kIC0gcmVpZ25fc3RhcnQpICU+JSAKICBncm91cF9ieShkeW5hc3R5KSAlPiUgCiAgc3VtbWFyaXNlKHJlaWduID0gc3VtKHJlaWduKSkgJT4lIAogIGFycmFuZ2UoZGVzYyhyZWlnbikpCmBgYAoKV2UgYWN0dWFsbHkgZ2V0IGhpZ2hlciBudW1iZXJzIHRoYW4gYnkKc3VidHJhY3RpbmcgdGhlIHJlaWduIHN0YXJ0IG9mIHRoZSBmaXJzdCBlbXBlcm9yIGluIGVhY2gKZHluYXN0eSBmcm9tIHRoZSByZWlnbiBlbmQgb2YgdGhlIGxhc3QgZW1wZXJvciBpbiB0aGUgZHluYXN0eSEKCmBgYHtyfQplbXBlcm9ycyAlPiUgCiAgYXJyYW5nZShyZWlnbl9zdGFydCkgJT4lIAogIGdyb3VwX2J5KGR5bmFzdHkpICU+JSAKICBzdW1tYXJpc2UocmVpZ24gPSBsYXN0KHJlaWduX2VuZCkgLSBmaXJzdChyZWlnbl9zdGFydCkpICU+JSAKICBhcnJhbmdlKGRlc2MocmVpZ24pKQpgYGAKClRoZSByZWFzb24gZm9yIHRoaXMgaXMgdGhhdCBzb21lIGVtcGVyb3JzIHN0YXJ0ZWQgdGhlaXIKcmVpZ24gd2l0aCB0aGUgcHJldmlvdXMgZW1wZXJvciBhbHNvIHN0aWxsIHJlaWduaW5nOgoKYGBge3J9CmVtcGVyb3JzICU+JSAKICBhcnJhbmdlKHJlaWduX3N0YXJ0KSAlPiUgCiAgbXV0YXRlKHRpbWVfYmV0d2VlbiA9IHRpbWVfbGVuZ3RoKHJlaWduX3N0YXJ0IC0gbGFnKHJlaWduX2VuZCksIHVuaXQgPSAieWVhciIpICkgJT4lIAogIGdncGxvdChhZXModGltZV9iZXR3ZWVuKSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGxhYnMoeCA9ICJZZWFycyBhZnRlciB0aGUgcHJldmlvdXMgZW1wZXJvciIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpJbiBzb21lIGNhc2VzIHRoaXMgaXMgcHJvYmFibHkgYSBkYXRhIHF1YWxpdHkgaXNzdWUsIGluIHNvbWUgY2FzZXMKaXQgbWlnaHQgYWN0dWFsbHkgYmUgdGhlIGNhc2UgdGhhdCBhbiBlbXBlcm9yIGRlY2lkZWQgdG8gbm93CmJlIHJlaWduaW5nIHdoaWxlIGFub3RoZXIgd2FzIHN0aWxsIG9uIHRoZSB0aHJvbmUuCgoKYGBge3IsIGZpZy5oZWlnaHQ9MTJ9CmVtcGVyb3JzICU+JSAKICBtdXRhdGUobmFtZSA9IGZjdF9yZW9yZGVyKG5hbWUsIHJlaWduX3N0YXJ0KSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHJlaWduX3N0YXJ0LCB4ZW5kID0gcmVpZ25fZW5kLCB5ID0gbmFtZSwgeWVuZCA9IG5hbWUsIGNvbG9yID0gZHluYXN0eSkpICsKICBnZW9tX3NlZ21lbnQoc2l6ZSA9IDIpICsKICAjIGdlb21fdGV4dChhZXMobGFiZWwgPSBuYW1lKSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZCgpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArCiAgbGFicyh4ID0gIlRpbWUiLCB5ID0gIiIsIHRpdGxlID0gIlJvbWFuIEVtcGVyb3JzIikgKwogIHNjYWxlX3lfZGlzY3JldGUoKQpgYGAKClRoZXJlIHdhcyBhIHF1ZXN0aW9uIGFib3V0IGNvbG9yaW5nIGluIHRoZSBuYW1lcyBvbiB0aGUgeS1heGlzLApoZXJlIGlzIGEgcGFja2FnZSB0byBoZWxwIHlvdSB3aXRoIHRoYXQ6IDxodHRwczovL3dpbGtlbGFiLm9yZy9nZ3RleHQvPgoKV2hpY2ggZHluYXN0eSB3b3VsZCB5b3UgcmF0aGVyIGJlIGEgcGFydCBvZiwKaWYgeW91ciBnb2FsIGlzIHRvIGxpdmUgdGhlIGxvbmdlc3Q/CiAKYGBge3J9CnBsdCA8LSBlbXBlcm9ycyAlPiUgCiAgbXV0YXRlKGxpZmUgPSBkZWF0aCAtIGJpcnRoKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShsaWZlKSkgJT4lIAogIGdncGxvdChhZXMoZHluYXN0eSwgbGlmZSkpICsKICBzdGF0X3N1bW1hcnkoY29sb3IgPSAiZ3JleTUwIikgKwogIGdlb21fcG9pbnQoYWVzKHRleHQgPSBuYW1lKSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbWluaW1hbCgpCgpwbG90bHk6OmdncGxvdGx5KHBsdCkKYGBgCgpBcyBhbHdheXMsIHRoZSBhbnN3ZXIgZGVwZW5kcy4KQXJlIHdlIHRoZSBnYW1ibGluZyB0eXBlPwpHb3JkaWFucyBoYXZlIHByZXR0eSBoaWdoIGNlaWxpbmcgZm9yIHRoZWlyIGxpZmVzcGFuIGJ1dCBhbHNvIGEgcHJldHR5IGxvdyBmbG9vci4uLgoKIyMgRGFpcnkgUHJvZHVjdHMgaW4gdGhlIFVTCgpBbm90aGVyIGRhdGFzZXQKKFtsaW5rXShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L3RyZWUvbWFzdGVyL2RhdGEvMjAxOS8yMDE5LTAxLTI5I21pbGtfcHJvZHVjdHNfZmFjdHMpKQpjb25jZXJucyBkYWlyeSBwcm9kdWN0IGNvbnN1bXB0aW9uIHBlciBwZXJzb24gaW4gdGhlIFVTIGFjcm9zcyBhIG51bWJlciBvZiB5ZWFycy4KCmBgYHtyfQpkYWlyeSA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDE5LzIwMTktMDEtMjkvbWlsa19wcm9kdWN0c19mYWN0cy5jc3YiKQpgYGAKCk5vdGU6IGFmdGVyIHBsYXlpbmcgYSBhcm91bmQgd2l0aCB0aGUgZGF0YSB3ZSB3b3VsZCBub3JtYWxseSB0aGVuIHB1dAphbGwgdGhlIGRhdGEgY2xlYW5pbmcgc3RlcHMgdXAgaGVyZSB3aGVyZSB3ZSBpbXBvcnQgdGhlIGRhdGEgc28gdGhhdAp3ZSBzZWUgaXQgc3RyYWlnaHQgYXdheS4KU29tZXRpbWVzIEkgbGlrZSB0byBjYWxsIHRoZSBpbml0aWFsbHkgaW1wb3J0ZWQgZGF0YXNldCBgPG5hbWU+X3Jhd2AsCmUuZy4gYGRhaXJ5X3Jhd2AgYW5kIHRoZW4gYWZ0ZXIgY2xlYW5pbmcgdGhlIGRhdGEgSSBzdG9yZSBpdAppbiBhIHZhcmlhYmxlIGxpa2UgYGRhaXJ5X3RpZHlgIG9yIGBkYWlyeWAuCgpUaGlzIGlzIHdoYXkgdGhlIGluaXRpYWwgZGF0YSBsb29rcyBsaWtlOgoKYGBge3J9CmRhaXJ5CmBgYAoKCkFsbCBtYXNzZXMgYXJlIGdpdmVuIGluIGxicyAocG91bmRzKSwgY2FuIHlvdSBjb252ZXJ0IHRoZW0gdG8ga2c/CgpUaGVyZSBhcmUgbXVsdGlwbGUgd2F5cyB0byBkbyB0aGF0IQoKYGBge3IsIGV2YWw9RkFMU0V9Cmxic190b19rZyA8LSBmdW5jdGlvbih4KSB4IC8gMi4yCgpkYWlyeSAlPiUgCiAgbXV0YXRlKGFjcm9zcygteWVhciwgbGJzX3RvX2tnKSkKYGBgCgpvciB3aXRoIGFuIGFub255bW91cyBmdW5jdGlvbjoKCmBgYHtyLCBldmFsPUZBTFNFfQpkYWlyeSAlPiUgCiAgbXV0YXRlKGFjcm9zcygteWVhciwgfiAueCAvIDIuMikpCmBgYAoKQnV0IHdlIGNhbiBhbHNvIHJlbWVtYmVyIG91ciB0aWR5IGRhdGEgcHJpbmNpcGxlcyBhbmQgZG8gYSBwaXZvdCBmaXJzdCEKCmBgYHtyfQpkYWlyeSA8LSBkYWlyeSAlPiUgCiAgcGl2b3RfbG9uZ2VyKC15ZWFyLCBuYW1lc190byA9ICJwcm9kdWN0IiwgdmFsdWVzX3RvID0gImNvbnN1bXB0aW9uIikgJT4lIAogIG11dGF0ZShjb25zdW1wdGlvbiA9IGNvbnN1bXB0aW9uIC8gMi4yKQpgYGAKCkFsbCBhcHByb2FjaGVzIGFyZSB2YWxpZCwgYnV0IHdlIGxpa2UgdGhlIHRpZHkgZGF0YSBmb3JtYXQgZm9yIHRoZQpyZXN0IG9mIHRoaXMgYW5hbHlzaXMuCk5vdGUgdGhvdWdoLCB0aGF0IHRoZSBmaXJzdCBhcHByb2FjaCBoYWQgdGhlIGFkZGVkIGJlbmVmaXQKb2YgZ2l2aW5nIGEgcmVhc29uYWJsZSBuYW1lIHRvIG91ciBjYWxjdWxhdGlvbiBgbGJzX3RvX2tnYCwKc28gdGhpcyB3aWxsIGJlIG5pY2UgaWYgd2UgaGF2ZSB0byBjb21lIGJhY2sgdG8gaXQgbGF0ZXIgdG8gc2VlCndoYXQgd2UgZGlkLiBSZWFkYWJpbGl0eSBjb3VudHMhCldlIGNvdWxkIGhhdmUgY29tYmluZWQgdGhlIGZpcnN0IGFuZCB0aGlyZCBhcHByb2FjaCBhcyB3ZWxsLiAKCldoaWNoIHByb2R1Y3RzIGxvc3QgdGhlaXIgY3VzdG9tZXIgYmFzZSBvdmVyIHRpbWUsCndoaWNoIG9uZXMgd29uPyBXaGljaCBwcm9kdWN0cyBoYXZlIHRoZSBncmVhdGVzdCBhYnNvbHV0ZQpjaGFuZ2UgaW4gcHJvZHVjdGlvbiB3aGVuIGVzdGltYXRlZCB3aXRoIGEgc3RyYWlnaHQgbGluZT8KClZpc3VhbGx5OgoKYGBge3IgZmlnLndpZHRoPTEyfQpkYWlyeSAlPiUgCiAgZ2dwbG90KGFlcyh5ZWFyLCBjb25zdW1wdGlvbiwgY29sb3IgPSBwcm9kdWN0KSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGFscGhhID0gMC4zKSArCiAgZ2VvbV9saW5lKCkgKwogIGZhY2V0X3dyYXAofnByb2R1Y3QsIHNjYWxlcyA9ICJmcmVlIikgKwogIGd1aWRlcyhjb2xvciA9ICJub25lIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh5ID0gIkNvbnN1bXB0aW9uIFtrZ10iKSArCiAgc2NhbGVfeF9jb250aW51b3VzKG4uYnJlYWtzID0gNSkKYGBgCgpOdW1lcmljYWxseToKCmBgYHtyfQptb2RlbHMgPC0gZGFpcnkgJT4lCiAgZ3JvdXBfYnkocHJvZHVjdCkgJT4lIAogIHN1bW1hcmlzZSgKICAgIG1vZGVsID0gbGlzdChsbShjb25zdW1wdGlvbiB+IHllYXIpKSwKICAgIHNsb3BlID0gbWFwX2RibChtb2RlbCwgfiBicm9vbTo6dGlkeSgueCkkZXN0aW1hdGVbMl0pCiAgKQoKbW9kZWxzICU+JSAKICBhcnJhbmdlKGFicyhzbG9wZSkpCmBgYAoKCiMgRnVydGhlciBub3RlcwoKIyMgVGlkeSBldmFsdWF0aW8KCkJ5IG5vdyBJIGhhdmUgaHlwZWQgdXAgd3JpdGluZyB5b3VyIG93biBmdW5jdGlvbnMgcXVpdGUgYSBiaXQKYW5kIHRoZXJlIHdpbGwgaW5ldml0YWJseSBjb21lIHRoZSBwb2ludCB3aGVuIHlvdSB3aWxsIHRyeQp0byB3cml0ZSBhIGZ1bmN0aW9uIHRoYXQgd29ya3MgbGlrZSBvbmUgb2YgdGhlIHRpZHl2ZXJzZSBmdW5jdGlvbnMuCgpVbmZvcnR1bmF0ZWx5IHRoZSB0cmFkZSBvZmYgb2YgYmVpbmcgbmljZSB0byB3b3JrIHdpdGggaW50ZXJhY3RpdmVseSwKbGlrZSBqdXN0IHJlZmVycmluZyB0byBjb2x1bW5zIGluIGEgZGF0YWZyYW1lIGJ5IHRoZWlyIG5hbWUgaW5zaWRlCm9mIHRpZHl2ZXJzZSBmdW5jdGlvbnMsIGNvbWVzIGF0IGEgcHJpY2UuCklmIHdlIHdhbnQgdGhlIHNhbWUsIHdlIG5lZWQgdG8gdGFrZSBvbiBleHRyYSBzdGVwLgoKSW4gdGhlIGZ1bmN0aW9uIGB0ZXN0YCBiZWxvdywgaWYgd2UgZGlkbid0IGhhdmUgdGhlCmRvdWJsZSBjdXJseSBicmFjZXMgYHt7fX1gLCBkcGx5ciB3b3VsZCBzZWFyY2ggZm9yIGEgY29sdW1uIG5hbWVkCmBjb2x1bW5gIGluIHRoZSBkYXRhc2V0IQpUaGlzIGlzIG9mIGNvdXJzZSBub3QgdGhlcmUsIHNvIHlvdSBnZXQgYW4gZXJyb3IuClRoZSBjdXJseS1jdXJseSBvcGVyYXRvciBhbGxvd3MgdXMgdG8gdGVsbCBkcGx5ciB0aGF0IHRoaXMgbmFtZQppcyBqdXN0IGEgcGxhY2Vob2xkZXIgZm9yIHdoYXQgdGhlIHVzZXIgb2Ygb3VyIGZ1bmN0aW9uIHdpbGwgc3VwcGx5OgoKYGBge3J9CnRlc3QgPC0gZnVuY3Rpb24oZGF0YSwgY29sdW1uKSB7CiAgZGF0YSAlPiUgCiAgICBjb3VudCgge3sgY29sdW1uIH19ICkKfQoKdGVzdChlbXBlcm9ycywgcmlzZSkKYGBgCgojIyBGaW5kaW5nIGhlbHAKCkxlYXJuaW5nIGhvdyB0byBmaW5kIGhlbHAgYW5kIGFzayBmb3IgaGVscCBpcyBjcnVjaWFsLgpUaGUgbW9zdCBpbXBvcnRhbnQgdGlwIGlzIGxlYXJuaW5nIGhvdyBhICJSZXByb2R1Y2libGUgRXhhbXBsZSIsCmEgInJlcHJleCIgd29ya3MKCjxodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL2hlbHAvI3JlcHJleD4KCk1vcmUgW1Jlc291cmNlc10oaHR0cHM6Ly9qbWJ1aHIuZGUvZGF0YWludHJvL3Jlc291cmNlcy01Lmh0bWwjZ2V0dGluZy1oZWxwLTEpLgoK